#include "ET++.ph"
#include "Object.h"         // cpp max nesting level...

#include "Class.h"
#include "Error.h"
#include "File.h"
#include "System.h"
#include "String.h"
#include "DevBitmap.h"
#include "OrdColl.h"
#include "Box.h"
#include "BorderItems.h"
#include "Buttons.h"
#include "Env.h"
#include "PopupItem.h"
#include "Menu.h"
#include "Fields.h"
#include "Math.h"
#include "ET_stdio.h"

#include "Printer.h"
#include "PrintPort.h"
#include "PrintManager.h"

const int
	cIdName       =   cIdFirstUser + 1,
	cIdOptions    =   cIdFirstUser + 10,
	cIdResolution =   cIdFirstUser + 20,
	cIdOrientation=   cIdFirstUser + 30,
	cIdPaperFormat=   cIdFirstUser + 40;

const int
	cFirstFontId  =   1174713,
	cFontMask     =   ~(eFaceUnderline|eFaceOutline|eFaceShadow);

const char *LpPath  =   "/usr/ucb/lpr -P%s";
		
static struct PaperFormat {
	char *name;
	int width, height;

	Point Size()
		{ return Point(width, height); }
} paperFormats[] = {
	{ "A4 Letter",            595,    842 },
	{ "B5 Letter",            421,    595 },
	{ "US Letter",            615,    750 },
	{ "US Legal",             615,    842 },
	{ "US Envelope Centered", 615,    300 },
	{ 0, 0, 0 }
};

//---- PostScriptSettings ------------------------------------------------------------

class PostScriptSettings : public PrinterSettings {
public:
	float resolution;
	bool portrait;
	bool smooth;
	bool wysiwyg;
	bool prolog;
	char *prname;
public:
	MetaDef(PostScriptSettings);
	PostScriptSettings();
	~PostScriptSettings();
	OStream& PrintOn(OStream &s);
	IStream& ReadFrom(IStream &s);
};

//---- PostScriptPort ----------------------------------------------------------

class PostScriptPort: public PrintPort {
friend class RFont;
	PostScriptSettings *pst;
	bool tofile;
	float xorigin, yorigin;
	FILE *pfp, *prfp;
	int lastpsz, maxfont, bcnt, pagecnt, currpage;
	Font *lbfont, *lfont;
	byte bbuf[400];
	OrdCollection *ProcSets, *Fonts;

protected:
	void SetPenSize(int pensize);
	void EnrollFont(Font *fd);
	void UseProcSet(char *name);
	const Rectangle *PrintBBox(const Rectangle &bbox);
	void downloadfont(FILE *ofp, Font *fd, char *fname);

public:
	MetaDef(PostScriptPort);
	PostScriptPort(const char *name, PostScriptSettings *pst);
	~PostScriptPort();

	void DevOpenPage(int pn);
	void DevClosePage();

	void DevSetPattern(Bitmap*);
	void DevSetOther(int);
	void DevSetGrey(float f);
	void DevSetColor(RGBColor *cp);

	void DevClip(const Rectangle &r);

	void DevStrokeLine(int, const Rectangle &r, GrLineCap, const Point&, const Point&);
	void DevStrokeRect(int, const Rectangle &r);
	void DevFillRect(const Rectangle &r);
	void DevStrokeOval(int, const Rectangle &r);
	void DevFillOval(const Rectangle &r);
	void DevStrokeRRect(int, const Rectangle &r, const Point&);
	void DevFillRRect(const Rectangle &r, const Point&);
	void DevStrokeWedge(int, GrLineCap, const Rectangle &r, int, int);
	void DevFillWedge(const Rectangle &r, int, int);
	void DevStrokePolygon(const Rectangle &r, Point*, int, GrPolyType t, int, GrLineCap);
	void DevFillPolygon(const Rectangle &r, Point*, int, GrPolyType t);
	void DevShowBitmap(const Rectangle &r, Bitmap*);
	void DevShowChar(Font *fdp, const Point&, Rune c, const Rectangle &r);
	void DevShowTextBatch(const Rectangle &r, const Point&);
	void DevGiveHint(int, int, void*);
	bool DevScale(float sx, float sy);
	void MaskImage(const Rectangle &r, Bitmap *bm);
};

//---- PostScriptPrinter -------------------------------------------------------

class PostScriptPrinter : public Printer {
	OneOfCluster *orientation;
	FloatField *resolution;
	ManyOfCluster *options;
	TextField *eti;
	Menu *paperFormatMenu;
	PopupButton *paperFormatPopup;
public:
	MetaDef(PostScriptPrinter);
	PostScriptPrinter()
		{ }
	const char *GetName()
		{ return "PostScript"; }
	PrintPort *MakePrintPort(const char *name)
		{ return new PostScriptPort(name, (PostScriptSettings*) settings); }
	bool CanSave()
		{ return TRUE; }
	bool CanPrint()
		{ return TRUE; }
	PrinterSettings *MakeSettings()
		{ return new PostScriptSettings; }
	VObject *DoCreatePrintDialog();
	void LoadSettings(PrinterSettings*);
	void StoreSettings(PrinterSettings*);
};

//---- FMap --------------------------------------------------------------------

static struct FMap {
	char *name;
	int encode;
	GrFont fid;
	GrFace face;
} fmap[] = {
	{ "AvantGarde-Book",                1, eFontAvantgarde, eFacePlain },
	{ "AvantGarde-BookOblique",         1, eFontAvantgarde, eFaceItalic },
	{ "AvantGarde-Demi",                1, eFontAvantgarde, eFaceBold },
	{ "AvantGarde-DemiOblique",         1, eFontAvantgarde, (GrFace)(eFaceItalic | eFaceBold) },
	{ "Bookman-Light ",                 1, eFontBookman, eFacePlain },
	{ "Bookman-LightItalic",            1, eFontBookman, eFaceItalic },
	{ "Bookman-Demi",                   1, eFontBookman, eFaceBold },
	{ "Bookman-DemiItalic",             1, eFontBookman, (GrFace)(eFaceItalic | eFaceBold) },
	{ "Courier",                        1, eFontCourier, eFacePlain },
	{ "Courier-Oblique",                1, eFontCourier, eFaceItalic },
	{ "Courier-Bold",                   1, eFontCourier, eFaceBold },
	{ "Courier-BoldOblique",            1, eFontCourier, (GrFace)(eFaceItalic | eFaceBold) },
	{ "Helvetica",                      1, eFontHelvetica, eFacePlain },
	{ "Helvetica-Oblique",              1, eFontHelvetica, eFaceItalic },
	{ "Helvetica-Bold",                 1, eFontHelvetica, eFaceBold },
	{ "Helvetica-BoldOblique",          1, eFontHelvetica, (GrFace)(eFaceItalic | eFaceBold) },
	{ "Helvetica-Narrow",               1, eFontNHelvetica, eFacePlain },
	{ "Helvetica-Narrow-Oblique",       1, eFontNHelvetica, eFaceItalic },
	{ "Helvetica-Narrow-Bold",          1, eFontNHelvetica, eFaceBold },
	{ "Helvetica-Narrow-BoldOblique",   1, eFontNHelvetica, (GrFace)(eFaceItalic | eFaceBold) },
	{ "NewCenturySchlbk-Roman",         1, eFontSchoolbook, eFacePlain },
	{ "NewCenturySchlbk-Italic",        1, eFontSchoolbook, eFaceItalic },
	{ "NewCenturySchlbk-Bold",          1, eFontSchoolbook, eFaceBold },
	{ "NewCenturySchlbk-BoldItalic",    1, eFontSchoolbook, (GrFace)(eFaceItalic | eFaceBold) },
	{ "Palatino-Roman",                 1, eFontPalatino, eFacePlain },
	{ "Palatino-Italic",                1, eFontPalatino, eFaceItalic },
	{ "Palatino-Bold",                  1, eFontPalatino, eFaceBold },
	{ "Palatino-BoldItalic",            1, eFontPalatino, (GrFace)(eFaceItalic | eFaceBold) },
	{ "Symbol",                         0, eFontSymbol, eFacePlain },
	{ "Times-Roman",                    1, eFontTimes, eFacePlain },
	{ "Times-Italic",                   1, eFontTimes, eFaceItalic },
	{ "Times-Bold",                     1, eFontTimes, eFaceBold },
	{ "Times-BoldItalic",               1, eFontTimes, (GrFace)(eFaceItalic | eFaceBold) },
	{ "ZapfChancery-MediumItalic",      1, eFontChancery, eFacePlain },
	{ "ZapfDingbats",                   0, eFontDingbats, eFacePlain },
	{ 0 }
};

static FMap *FindFont(GrFont fid, GrFace face)
{
	int i;
	for (i= 0; fmap[i].name; i++)
		if (fmap[i].fid == fid && fmap[i].face == face)
			return &fmap[i];
	return 0;
}

static const char *MapName(GrFont fid, GrFace face)
{
	FMap *fm= FindFont(fid, (GrFace)(face & cFontMask));
	if (fm)
		return fm->name;
	return gFontManager->IdToName(fid);
}

static bool IsPSFont(GrFont fid)
{
	FMap *fm= FindFont(fid, eFacePlain);
	if (fm)
		return TRUE;
	return FALSE;
}

static bool Reencode(GrFont fid)
{
	FMap *fm= FindFont(fid, eFacePlain);
	if (fm)
		return fm->encode;
	return FALSE;
}

static char *EtName(GrFont fid, GrFace face)
{
	return form("%s-%s", gFontManager->IdToName(fid),
		gFontManager->StyleString((GrFace)(face & cFontMask)));
}

//---- output functions ---------------------------------------------------------

/*
%c  PostScript character       byte
%f  floating point             float
%d  integer                    integer
%x  2-digit hex num            byte
%r  Rectangle                  Rectangle*
%p  Point                      Point
%s  0-terminated string        char*
*/

static void Printf(FILE *pfp, char *va_(format), ...)
{
	static char *t= "0123456789abcdef";
	register char c;
	unsigned int b;
	char *s;
	Point *p;
	int i;
	double d;
	Rectangle *r;
	va_list ap;

	va_start(ap,va_(format));
	char *fmt= va_(format);

	while (c= *fmt++) {
		switch (c) {
		case '%':
			switch (c= *fmt++) {
			case '%':
				fputc('%', pfp);
				break;
			case 'c':
				b= va_arg(ap, unsigned int);
				if (b >= ' ' && b <= '~') {
					if (b == '\\' || b == '(' || b == ')')
						fputc('\\', pfp);
					fputc(b, pfp);
				} else
					fprintf(pfp, "\\%03o", b);
				break;
			case 'f':
				d= va_arg(ap, double);
				fprintf(pfp, "%.3f", d);
				break;
			case 'd':
				i= va_arg(ap, int);
				fprintf(pfp, "%d", i);
				break;
			case 'x':
				b= va_arg(ap, unsigned int);
				fputc(t[(b&0xff)>>4], pfp);
				fputc(t[b&0xf], pfp);
				break;
			case 's':
				s= va_arg(ap, char*);
				while (c= *s++)
					fputc(c, pfp);
				break;
			case 'r':
				r= va_arg(ap, Rectangle*);
				fprintf(pfp, "%d %d %d %d", r->origin.x, r->origin.y, r->extent.x,
											r->extent.y);
				break;
			case 'p':
				p= va_arg(ap, Point*);
				fprintf(pfp, "%d %d", (int) p->x, (int) p->y);
				break;
			default:
				fputc(c, pfp);
				break;
			}
			break;
		default:
			fputc(c, pfp);
			break;
		}
	}
	va_end(ap);
}

//---- PostScriptSettings ------------------------------------------------------------

NewMetaImpl(PostScriptSettings, PrinterSettings, (T(resolution), TB(portrait),
					TB(smooth), TB(wysiwyg), TB(prolog), TS(prname)));

PostScriptSettings::PostScriptSettings()
			: PrinterSettings(Point(595, 842), Point(50), Point(50), gRect0)
{
	portrait= prolog= TRUE;
	smooth= wysiwyg= FALSE;
	resolution= 1.0;
	prname= strsave(Env::GetValue("PRINTER", "lp"));
}

PostScriptSettings::~PostScriptSettings()
{
	SafeDelete(prname);
}

OStream &PostScriptSettings::PrintOn(OStream &s)
{
	PrinterSettings::PrintOn(s);
	return s << portrait SP << prolog SP << smooth SP
				<< wysiwyg SP << resolution SP << prname SP;
}

IStream  &PostScriptSettings::ReadFrom(IStream &s)
{
	char buf[100];
	PrinterSettings::ReadFrom(s);
	s >> portrait >> prolog >> smooth >> wysiwyg >> resolution >> buf;
	SafeDelete(prname);
	prname= strsave(buf);
	return s;
}

//---- PSResource --------------------------------------------------------------

class PSResource : public Object {
	static char *lasttag;
protected:
	char *type, *name;
	bool supplied;
public:
	PSResource(char *t, bool suppl)
		{ type= t; supplied= suppl; name= 0; }
	~PSResource()
#if defined(sgi)
		{ if (name) delete name; name= 0; }     // FIXME: sgi bug
#else
		{ SafeDelete(name); }
#endif
	const char *GetName()
		{ return name; }
	void Load(FILE *ofp);
	virtual void load(FILE *ofp) = 0;
	void Do(FILE *ofp, char *t);
	void Used(FILE *ofp)
		{ Do(ofp, ""); }
	void Supplied(FILE *ofp)
		{ if (supplied) Do(ofp, "Supplied"); }
	void Needed(FILE *ofp)
		{ if (!supplied) Do(ofp, "Needed"); }
};

char *PSResource::lasttag;

void PSResource::Load(FILE *ofp)
{
	if (supplied)
		load(ofp);
	else
		Printf(ofp, "%%%%Include%s: %s\n", type, name);
}

void PSResource::Do(FILE *ofp, char *t)
{
	if (lasttag == 0 || strcmp(lasttag, t) != 0) {
		Printf(ofp, "%%%%Document%s%ss: %s\n", t, type, name);
		lasttag= t;
	} else
		Printf(ofp, "%%%%+ %s\n", name);
}

//---- ProcSet -----------------------------------------------------------------

class ProcSet : public PSResource {
public:
	ProcSet(char *n, bool pro) : PSResource("ProcSet", pro)
		{ name= strsave(n); }
	void load(FILE *ofp);
};

void ProcSet::load(FILE *ofp)
{
	char buf[256];
	for (int i= 0; name[i]; i++) {
		if (name[i] == ' ')
			buf[i]= '-';
		else
			buf[i]= name[i];
	}
	buf[i]= 0;
	FILE *fin= fopen(form("%s/postscript/%s.ps", gEtDir, buf), "r");
	if (fin) {
		while (fgets(buf, sizeof(buf), fin))
			fputs(buf, ofp);
		fclose(fin);
	} else
		fprintf(stderr, "can't load procset <%s>\n", buf);
}

//---- RFont -------------------------------------------------------------------

class RFont : public PSResource {
	PostScriptPort *psp;
public:
	GrFont fid;
	GrFace face;
public:
	RFont(PostScriptPort *p, GrFont fd, GrFace fc);
	void load(FILE *ofp)
		{  psp->downloadfont(ofp, new_Font(fid, 12, face, TRUE), name); }
	void SetUp(FILE *ofp);
};

RFont::RFont(PostScriptPort *p, GrFont fd, GrFace fc)
					: PSResource("Font", !IsPSFont(fd))
{
	psp= p;
	fid= fd;
	face= fc;
	name= strsave(MapName(fd, fc));
}

void RFont::SetUp(FILE *ofp)
{
	Printf(ofp, "/%s /%s findfont def\n", EtName(fid, face), name);
	if (Reencode(fid))
		Printf(ofp, "/%s Reencode\n", EtName(fid, face));
}

//---- PostScriptPort ----------------------------------------------------------

NewMetaImpl0(PostScriptPort, PrintPort);

PostScriptPort::PostScriptPort(const char *name, PostScriptSettings *st)
								: PrintPort()
{
	pst= st;

	hascolor= TRUE;

	ProcSets= new OrdCollection;
	Fonts= new OrdCollection;

	xorigin= 0.0;
	yorigin= (pst->portrait) ? 842.0 : 0.0;

	tofile= (name != 0);
	pagecnt= 0;

	UseProcSet("et 2.2 0");

	if (tofile)
		prfp= fopen(name, "w");
	else
		prfp= gSystem->OpenPipe(form(LpPath, pst->prname), "w");

	if ((pfp= tmpfile()) == NULL)
		fprintf(stderr, "PostScriptPort::PostScriptPort: can't open tmpfile\n");

	Printf(prfp, "%%!PS-Adobe-1.0\n");
	Printf(prfp, "%%%%Title: title\n");
	Printf(prfp, "%%%%Creator: ET++\n");
	Printf(prfp, "%%%%CreationDate: date\n");
	Printf(prfp, "%%%%For: User\n");
	Printf(prfp, "%%%%DocumentData: Clean7Bit\n");
	Printf(prfp, "%%%%LanguageLevel: 1\n");
	Printf(prfp, "%%%%Orientation: %s\n", pst->portrait ? "Portrait"
							   : "Landscape");
	Printf(prfp, "%%%%PageOrder: Ascend\n");
	Printf(prfp, "%%%%Version: 2.2 1\n");
}

PostScriptPort::~PostScriptPort()
{
	FlushMyText();

	Printf(pfp, "%%%%Trailer\n");
	Printf(pfp, "end\n");
	Printf(pfp, "%%%%EOF\n");

	{ ProcSets->ForEach(ProcSet,Used)(prfp); }
	{ ProcSets->ForEach(ProcSet,Supplied)(prfp); }
	{ ProcSets->ForEach(ProcSet,Needed)(prfp); }

	{ Fonts->ForEach(RFont,Used)(prfp); }
	{ Fonts->ForEach(RFont,Supplied)(prfp); }
	{ Fonts->ForEach(RFont,Needed)(prfp); }

	Printf(prfp, "%%%%Pages: %d\n", pagecnt);
	Printf(prfp, "%%%%BoundingBox: %r\n", PrintBBox(bbox));
	Printf(prfp, "%%%%EndComments\n");

	Printf(prfp, "%%%%BeginProlog\n");
	{ ProcSets->ForEach(ProcSet,Load)(prfp); }
	Printf(prfp, "%%%%EndProlog\n");

	Printf(prfp, "%%%%BeginSetup\n");
	Printf(prfp, "ET++Dict begin\n");
	{ Fonts->ForEach(RFont,Load)(prfp); }
	{ Fonts->ForEach(RFont,SetUp)(prfp); }

	Printf(prfp, "%%%%EndSetup\n");

	{
		int n;
		char buf[4096];
		rewind(pfp);
		do {
			n= fread(buf, 1, 4096, pfp);
			if (n > 0)
				fwrite(buf, 1, n, prfp);
		} while (!feof(pfp) && (n == 4096));
		fclose(pfp);
	}
		
	if (tofile)
		fclose(prfp);
	else
		gSystem->ClosePipe(prfp);
		
	if (ProcSets) {
		ProcSets->FreeAll();
		SafeDelete(ProcSets);
	}
	if (Fonts) {
		Fonts->FreeAll();
		SafeDelete(Fonts);
	}
}

const Rectangle *PostScriptPort::PrintBBox(const Rectangle &bbox)
{
	static Rectangle r;
	r.origin.x= (int)(xorigin + pst->resolution*bbox.origin.x - 0.5),
	r.origin.y= (int)(yorigin - pst->resolution*(bbox.origin.y + bbox.extent.y) - 0.5),
	r.extent.x= (int)(xorigin + pst->resolution*(bbox.origin.x + bbox.extent.x) + 0.5),
	r.extent.y= (int)(yorigin - pst->resolution*bbox.origin.y + 0.5);
	return &r;
}

//---- Open, Close -------------------------------------------------------------

void PostScriptPort::DevOpenPage(int pn)
{
	lastpsz= -1;
	lbfont= 0;
	currpage= pn;
	Printf(pfp, "%%%%Page: %d %d\n", pagecnt+1, currpage);
	Printf(pfp, "%%%%PageBoundingBox: (atend)\n");
	Printf(pfp, "%%%%BeginPageSetup\n");
	Printf(pfp, "save\n");
	Printf(pfp, "%f %f %d %f init\n", xorigin, yorigin, pst->portrait ? 0 : 90,
									pst->resolution);
	Printf(pfp, "%%%%EndPageSetup\n");
}

void PostScriptPort::DevClosePage()
{
	Printf(pfp, "Restore\n");
	if (pagebbox.IsNotEmpty()) {
		if (gDebug)
			Printf(pfp, "%r StrokeBBox\n", PrintBBox(pagebbox));
		Printf(pfp, "showpage\n");
		pagecnt++;
	}
	Printf(pfp, "%%%%PageTrailer\n");
	Printf(pfp, "%%%%PageBoundingBox: %r\n", PrintBBox(pagebbox));
}

//---- clipping ----------------------------------------------------------------

void PostScriptPort::DevClip(const Rectangle &r)
{
	Printf(pfp, "%r SetClip\n", &r);
	lastpsz= -1;
	lbfont= 0;
}

bool PostScriptPort::DevScale(float sx, float sy)
{
	Printf(pfp, "%f %f scale\n", sx, sy);
	return TRUE;
}

//---- color -------------------------------------------------------------------

void PostScriptPort::DevSetGrey(float f)
{
	Printf(pfp, "%f SetGray\n", f);
}

void PostScriptPort::DevSetColor(RGBColor *cp)
{
	Printf(pfp, "%f %f %f setrgbcolor\n", cp->GetRed() / 255.0,
						cp->GetGreen() / 255.0,
						cp->GetBlue() / 255.0);
}

void PostScriptPort::DevSetPattern(Bitmap *bm)
{
	BitmapInfo bi(bm->Size(), bm->GetDepth(), 1);
	DevBitmap *bmp= bm->GetDevBitmap();
	byte row[16];
	int h= bm->Size().y;

	Printf(pfp, "<");
	for (int y= 0; y < h; y++) {
		bmp->GetRow(&bi, y, row);
		Printf(pfp, "%x", row[0]);
	}
	Printf(pfp, "> SetPattern\n");
}

void PostScriptPort::DevSetOther(int pid)
{
	Printf(pfp, "%f SetGray\n", pid == 1 ? 1.0 : 0.0);
}

void PostScriptPort::SetPenSize(int pensize)
{
	if (pensize < 0 || (pensize == lastpsz))
		return;
	lastpsz= pensize;
	Printf(pfp, "%d SetPenSize\n", pensize);
}

//---- drawing -----------------------------------------------------------------

void PostScriptPort::DevStrokeLine(int pensize, const Rectangle &r,
				GrLineCap cap, const Point &p1, const Point &p2)
{
	SetPenSize(pensize);
	Merge(r);
	Printf(pfp, "%p %p %d StrokeArrow\n", &p1, &p2, cap);
}

void PostScriptPort::DevStrokeRect(int psz, const Rectangle &r)
{
	Merge(r);
	SetPenSize(psz);
	Printf(pfp, "%r StrokeRect\n", &r);
}

void PostScriptPort::DevFillRect(const Rectangle &r)
{
	Merge(r);
	Printf(pfp, "%r FillRect\n", &r);
}

void PostScriptPort::DevStrokeOval(int psz, const Rectangle &r)
{
	Merge(r);
	SetPenSize(psz);
	Printf(pfp, "%r StrokeOval\n", &r);
}

void PostScriptPort::DevFillOval(const Rectangle &r)
{
	Merge(r);
	Printf(pfp, "%r FillOval\n", &r);
}

void PostScriptPort::DevStrokeRRect(int psz, const Rectangle &r, const Point &dia)
{
	Merge(r);
	SetPenSize(psz);
	Printf(pfp, "%r %p StrokeRoundRect\n", &r, &dia);
}

void PostScriptPort::DevFillRRect(const Rectangle &r, const Point &dia)
{
	Merge(r);
	Printf(pfp, "%r %p FillRoundRect\n", &r, &dia);
}

void PostScriptPort::DevStrokeWedge(int psz, GrLineCap, const Rectangle &r, int s, int d)
{
	Point e(s, d);
	Merge(r);
	SetPenSize(psz);
	Printf(pfp, "%r %p StrokeWedge\n", &r, &e);
}

void PostScriptPort::DevFillWedge(const Rectangle &r, int s, int d)
{
	Point e(s, d);
	Merge(r);
	Printf(pfp, "%r %p FillWedge\n", &r, &e);
}

void PostScriptPort::DevStrokePolygon(const Rectangle &r, Point *pts,
					int npts, GrPolyType t, int psz, GrLineCap cap)
{
	Merge(r);
	SetPenSize(psz);

	if (cap & eStartArrow) {
		Point p1(pts[1]+r.origin), p2(pts[0]+r.origin);
		Printf(pfp, "%p %p %d StrokeArrow\n", &p1, &p2, cap);
	}
	if (cap & eEndArrow) {
		Point p1(pts[npts-2]+r.origin), p2(pts[npts-1]+r.origin);
		Printf(pfp, "%p %p %d StrokeArrow\n", &p1, &p2, cap);
	}

	Printf(pfp, "%r %d %d %p [\n", &r, (pts[0] == pts[npts-1]) ? 1 : 0,
						(t & ePolyBezier) ? 1 : 0, &r.extent);
	for (int i= 0; i < npts; i++)
		Printf(pfp, "%p ", &pts[i]);
	Printf(pfp, "\n] StrokePoly\n");
}

void PostScriptPort::DevFillPolygon(const Rectangle &r, Point *pts, int npts, GrPolyType t)
{
	Merge(r);
	Printf(pfp, "%r %d %d %p [\n", &r, (pts[0] == pts[npts-1]) ? 1 : 0,
						(t & ePolyBezier) ? 1 : 0, &r.extent);
	for (int i= 0; i < npts; i++)
		Printf(pfp, "%p ", &pts[i]);
	Printf(pfp, "\n] FillPoly\n");
}

//---- text batching -----------------------------------------------------------

void PostScriptPort::DevShowChar(Font *fdp, const Point &delta, Rune c,
														const Rectangle&)
{
	if (fdp != lfont || delta != gPoint0)
		FlushAnyText();
		
	if (bcnt == 0)  // first
		lfont= fdp;

	bbuf[bcnt++]= c;
}

void PostScriptPort::DevShowTextBatch(const Rectangle &r, const Point &pos)
{
	int i, w= 0;
	register byte c;

	Merge(r);
	if (lbfont != lfont) {
		EnrollFont(lfont);
		Printf(pfp, "%s %d scalesetfont\n",
			EtName(lfont->Fid(), lfont->Face()), lfont->Size());
		lbfont= lfont;
	}
	if (bcnt > 0) {
		Printf(pfp, "%p m(", &pos);
		for (i= 0; i < bcnt; i++) {
			c= bbuf[i];
			w+= lfont->Width(c);
			Printf(pfp, "%c", c);
		}
		if (bcnt > 1)
			Printf(pfp, ") %d sshow\n", w);
		else
			Printf(pfp, ") show\n");
		bcnt= 0;
	}
}

//---- bitmaps & images --------------------------------------------------------

static byte bits[]= {
	0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01
};

void PostScriptPort::MaskImage(const Rectangle &r, Bitmap *bm)
{
	register int i, x, y;
	register byte *b;

	int w= bm->Size().x,
		h= bm->Size().y,
		bpl= ((w-1)/8+1),
		len= bpl*h,
		cmapsize= (int) bm->MaxPixelValue(),
		c;
	bool dirty;
	RGB rgb;
	byte *buf= new byte[len];

	UseProcSet("BitmapMask 3.0 0");

	Printf(pfp, "gsave\n");
	for (c= 0; c <= cmapsize; c++) {
		bm->PixelToRGB(c, &rgb);
		if (rgb.alpha == 0)
			continue;
		dirty= FALSE;
		b= buf;
		for (y= 0; y < h; y++) {
			for (x= 0; x < w; x++) {
				if (bm->GetPixel(x, y) == c) {
					dirty= TRUE;
					b[x >> 3] |= bits[x & 0x7];
				}
			}
			b+= bpl;
		}
		if (dirty) {
			Printf(pfp, "%f %f %f setrgbcolor\n",
				rgb.red / 255.0, rgb.green / 255.0, rgb.blue / 255.0);
			Point e(bm->Size());
			Printf(pfp, "%r %p BitmapMask\n", &r, &e);
			for (i= 0; i < len; i++) {
				Printf(pfp, "%x", buf[i]);
				if (((i+1) % 40) == 0)
					Printf(pfp, "\n");
			}
			Printf(pfp, "\n");
			memset(buf, 0, len);
		}
	}
	Printf(pfp, "grestore\n");
}

void PostScriptPort::DevShowBitmap(const Rectangle &r, Bitmap *bm)
{
	int bytes, x, y, by, lines, yy, depth= bm->GetDepth();
	Point e(bm->Size());
	DevBitmap *bmp= bm->GetDevBitmap();

	Merge(r);

	if (bm->GetColorMapper()) {
		MaskImage(r, bm);
		return;
	}

	BitmapInfo bi(e, depth, 1);

	if (!gDebug && pst->smooth) {
		bytes= bm->ShortsPerLine() * 2;
		lines= 1250 / bytes;
		
		UseProcSet("smooth 2.2 0");
		Printf(pfp, "%d %d div %d %d div %p %d %d SmoothBitmapStart\n",
				r.extent.x, e.x, r.extent.y, e.y, &r.origin, bytes, e.x);
		byte *row= new byte[bytes];
		
		for (by= 0, y= 0; y < e.y; y+= by) {
			by= Math::Min(lines, e.y-y);
			Printf(pfp, "%d Band\n", by);
			for(yy= y-2; yy < y+by+2; yy++) {
				bmp->GetRow(&bi, yy, row);
				for (x= 0; x < bytes; x++)
					Printf(pfp, "%x", row[x]);
				Printf(pfp, "\n");
			}
		}
		delete row;
		Printf(pfp, "SmoothBitmapEnd\n");
	} else {
		Printf(pfp, "%r %p %d Bitmap\n", &r, &e, depth);
		if (depth == 1) {
			bytes= bmp->BytesPerLine(); // postscript images are Byte aligned
			byte *row= new byte[bytes];
			for (y= 0; y < e.y; y++) {
				bmp->GetRow(&bi, y, row);
				for (x= 0; x < bytes; x++)
					Printf(pfp, "%x", row[x]);
				Printf(pfp, "\n");
			}
			delete row;
		} else {
			RGB rgb;

			for (y= 0; y < e.y; y++) {
				for (x= 0; x < e.y; x++) {
					bm->GetRGB(x, y, &rgb);
					Printf(pfp, "%x", rgb.AsGreyLevel());
				}
				Printf(pfp, "\n");
			}
		}
	}
}

//---- hints -------------------------------------------------------------------

void PostScriptPort::DevGiveHint(int code, int, void *vp)
{
	char buf[300];

	switch (code) {

	case 55:
		Printf(pfp, "%d %d translate\n", origin.x, origin.y);
		break;

	case 55559:
		if (sscanf((char*)vp, "%s", buf) == 1) {
			FILE *fp;
			if (fp= fopen(buf, "r")) {
				while (fgets(buf, 299, fp))
					if (buf[0] != '%')
						fputs(buf, pfp);
				fclose(fp);
			}
		}
		break;
	case eHintPostScript:
		Printf(pfp, "%s", (char*) vp);
		break;
	case eHintEPSFStart:
		Printf(pfp, "BeginEPSF\n");
		ignore= TRUE;
		break;
	case eHintEPSFEnd:
		Printf(pfp, "EndEPSF\n");
		ignore= FALSE;
		break;
	case 66666:
		{
			byte *dash= (byte*) vp;
			Printf(pfp, "[");
			for (int i= 0; i < dash[2]; i++)
				Printf(pfp, " %d", dash[i+3]);
			Printf(pfp, "] %d setdash\n", dash[0]);
		}
		break;
	case 66667:
		Printf(pfp, "%s", "[] 0 setdash\n");
		break;
	case 77777: // RotateBegin
		Printf(pfp, "%s", (char*) vp);
		break;
	case 77778: // RotateBegin
		Printf(pfp, "%s", (char*) vp);
		break;
	}
}

//---- misc functions ---------------------------------------------------------

void PostScriptPort::EnrollFont(Font *fd)
{
	Iter next(Fonts);
	register RFont *rf;

	while (rf= (RFont*) next())
		if (fd->Fid() == rf->fid && fd->Face() == rf->face)
			return;
		
	Fonts->Add(new RFont(this, fd->Fid(), fd->Face()));
}

void PostScriptPort::UseProcSet(char *name)
{
	Iter next(ProcSets);
	register ProcSet *ps;

	while (ps= (ProcSet*) next())
		if (strcmp(name, ps->GetName()) == 0)
			return;
		
	ProcSets->Add(new ProcSet(name, pst->prolog));
}

void PostScriptPort::downloadfont(FILE *ofp, Font *fd, char *fname)
{
	int z, i, x, y, ps, llx, lly, urx, ury, bytesperline;
	DevBitmap *dbm;
	Point offset, size;

	llx= lly= 9999;
	urx= ury= 0;
	ps= fd->Size();

	Printf(ofp, "%%%%BeginFont: %s\n", fname);
	Printf(ofp, "/%s %d %d [\n", fname, cFirstFontId+fd->Fid()*256+fd->Face(), ps);
	for (z= i= 0; i < cCharsPerFont; i++) {
		if ((dbm= fd->CharAsBitmap(i, &offset)) == 0)
			continue;
		dbm->Ref();
		size= dbm->Size();
		BitmapInfo bi(size, 1, 1);

		llx= Math::Min(llx, offset.x);
		lly= Math::Min(lly, offset.y);
		urx= Math::Max(urx, offset.x+size.x);
		ury= Math::Max(ury, offset.y+size.y);
		
		Printf(ofp, "<%x%x%x%x%x%x", i, fd->Width(i), size.x, size.y, offset.x,
																offset.y-size.y);
		byte *row;
		if (pst->smooth) {
			UseProcSet("smooth 2.2 0");
			bytesperline= (((size.x+2)-1)/16+1)*2;
			row= new byte[bytesperline];
			for (y= -2; y < size.y+2; y++) {
				dbm->GetRow(&bi, y, row);
				for (x= 0; x < bytesperline; x++)
					Printf(ofp, "%x", row[x]);
			}
		} else {
			bytesperline= (size.x-1) / 8 + 1;
			row= new byte[bytesperline+2];
			for (y= 0; y < size.y; y++) {
				dbm->GetRow(&bi, y, row);
				for (x= 0; x < bytesperline; x++)
					Printf(ofp, "%x", row[x]);
			}
		}
		delete row;
		
		Printf(ofp, ">\n");
		z++;
		dbm->Unref();
	}
	Printf(ofp, "] %d %d %d %d %d new%sbitmapfont\n", z, llx, lly, urx, ury,
												pst->smooth ? "smoothed" : "");
	Printf(ofp, "%%%%EndFont\n");
}

//---- PostScriptPrinter -------------------------------------------------------

NewMetaImpl(PostScriptPrinter, Printer, (TP(orientation), TP(resolution), TP(options), TP(eti)));

void PostScriptPrinter::LoadSettings(PrinterSettings *ps)
{
	if (eti) {
		PostScriptSettings *settings= (PostScriptSettings*) ps;
		options->SetItem(cIdOptions+0, settings->smooth);
		options->SetItem(cIdOptions+1, settings->wysiwyg);
		options->SetItem(cIdOptions+2, settings->prolog);
		resolution->SetValue(settings->resolution*100.0);
		orientation->SetItemOn(cIdOrientation + (settings->portrait ? 0 : 1));
		eti->SetString(settings->prname);
		
		Point pap= settings->paperSize;
		if (!settings->portrait)
			pap= Flip(pap);
		
		for (int i= 0; paperFormats[i].name; i++)
			if (paperFormats[i].Size() == pap)
				break;
		if (paperFormats[i].name == 0)
			paperFormatMenu->AppendItem(form("%d x %d points", pap.x, pap.y), i);
		paperFormatPopup->SetValue(i);
	}
}

void PostScriptPrinter::StoreSettings(PrinterSettings *ps)
{
	PostScriptSettings *settings= (PostScriptSettings*) ps;
	settings->smooth= options->IsItemOn(cIdOptions+0);
	settings->wysiwyg= options->IsItemOn(cIdOptions+1);
	settings->prolog= options->IsItemOn(cIdOptions+2);
	settings->resolution= resolution->GetValue()/100.0;

	int si= paperFormatPopup->GetValue();
	int maxsize= (sizeof(paperFormats)/sizeof(PaperFormat))-1;
	if (si >= 0 && si < maxsize)
		settings->SetPaperSize(paperFormats[si].Size());
	paperFormatMenu->RemoveItem(maxsize);

	settings->portrait= orientation->GetCurrentItem() == cIdOrientation;
	if (!settings->portrait)
		settings->SetPaperSize(Flip(settings->GetPaperSize()));

	SafeDelete(settings->prname);
	settings->prname= strsave(eti->AsString());
}

VObject *PostScriptPrinter::DoCreatePrintDialog()
{
	orientation=
		new OneOfCluster(cIdOrientation, eVObjHLeft, 5,
			"Portrait",
			"Landscape",
			0
		);
		
	options=
		new ManyOfCluster(cIdOptions, eVObjHLeft, 5,
			"Smooth Bitmaps",
			"Smooth Text",
			"Include ProcSets",
			0
		);
				
	paperFormatMenu= new Menu("Paper Formats", FALSE);
	for (int i= 0; paperFormats[i].name; i++)
		paperFormatMenu->AppendItem(paperFormats[i].name, i);

	//---- overall layout ----
	return
		new VExpander(10,
			new HBox(10, eVObjVTop,
				new BorderItem("Options", options),
				new BorderItem("Orientation", orientation),
				0
			),
			new Form(cIdNone, eVObjHCenter, 2,
				"Paper:", paperFormatPopup= new PopupButton(cIdPaperFormat,
														0, paperFormatMenu),
				"Scale:", resolution= new FloatField(cIdResolution, 25.0, 400.0, "%g %%"),
				"Printer Name:", eti= new TextField(cIdName, 10),
				0
			),
			0
		);
}

